Embed Lua into C++

2024-08-05

最后编辑于:2024-09-15

    #Lua
    #Cpp

构建Lua执行上下文

创建与销毁

lua_State* L = luaL_newstate();

lua_close(L);

C调用lua

function add(a, b)
	return a + b
end
// 将函数推至栈顶
lua_getglobal(L, "add")
// 传参
lua_pushnumber(L, a);
lua_pushnumber(L, b);
// 调用
lua_call(L, 2, 1);
// 获取返回值
lua_Number c = lua_tonumber(L, -1);

lua调用C

int add(lua_State* L) {
	lua_Number a = lua_tonumber(L, -2);
	lua_Number b = lua_tonumber(L, -1);
	lua_Number c = a + b;
	lua_pushnumber(L, c);
}
add(1, 2)
lua_pushcfunction(L, add);
lua_setglobal(L, "add");

注册函数的语法糖

lua_register(L, "add", add);

这个宏实际是对上面两个调用的简写

table

例如

x = { name = "w6rsty", age = 18 }

获取字段

lua_getglobal(L, "x");
lua_pushtring(L, "name");
lua_gettable(L, -2);
const char* name = lua_tostring(L, -1);
// 注意push的string键还在栈上,继续访问需要先pop

lua_getglobal(L, "x");
lush_pushstring(L, "age");
lua_gettable(L, -2);
lua_Number age = lua_tonumber(L, -1);

lua CAPI也提供了语法糖

lua_getglobal(L, "x");
lua_getfield(L, -1, "name");

写入字段

lua_getglobal(L, "x");
lua_pushstring(L, "coding");
lua_setfield(L, -2, "status");

MetaTables & MetaMethods

我们想要实现这样的操作

v1 = CreateVector()
v2 = CreateVector()
v1.x = 12
v1.y = 31
v3 = v1 + v3

其中v1,v2,v3是table

通过定义MetaTable和MetaMethods,可以进行table间的操作

struct Vector {
    static int Create(lua_State* L) {
        lua_newtable(L);
        lua_pushstring(L, "x");
        lua_pushnumber(L, 0);
        lua_settable(L, -3);

		// y

        luaL_getmetatable(L, "VectorMetaTable");
        lua_setmetatable(L, -2);
        return 1;
    }

    static int __add(lua_State* L) {
        lua_pushstring(L, "x");
        lua_gettable(L, -3);
        lua_Number xLeft = lua_tonumber(L, -1);
        lua_pop(L, 1);

        // yLeft
        // xRight
        // yRight

        Vector::Create(L);
        lua_pushstring(L, "x");
        lua_pushnumber(L, xLeft + xRight);
        lua_rawset(L, -3);
        lua_pushstring(L, "y");
        lua_pushnumber(L, yLeft + yRight);
        lua_rawset(L, -3);
        return 1;
    }
}

此处在创建新的Vector时使用lua_rawset,不会在metatable中查找,避免循环调用

lua_pushcfunction(L, Vector::Create);
lua_setglobal(L, "CreateVector");

luaL_newmetatable(L, "VectorMetaTable");
lua_pushstring(L, "__add");
lua_pushcfunction(L, Vector::__add);
lua_settable(L, -3);

lua中metatable里特殊的键,名称使用__开头 http://lua-users.org/wiki/MetatableEvents

Calling Constructor & Deconstructor

Constructor

要注意到lua_newuserdata只是分配对应结构的大小,返回内存起始点的指针,为了调用构造函数,需要使用placement new,在指针指向的位置进行构造

#include <new> //注意引入头文件
auto CreateSprite = [](lua_State* L) -> int {
	void* ptr = lua_newuserdata(L, sizeof(Sprite));
	new (ptr) Sprite();
	return 1;
};

userdata类型数据的内存分配由lua负责,并使用gc进行回收 这也意味着在gc时不会主动调用类的析构函数,需要手动在lua回收时的metamethod中进行调用

Deconstructor

__gc中调用析构 首先在创建时添加一个metatable,所有对象都共用这个表

auto CreateSprite = [](lua_State* L) -> int {
	void* ptr = lua_newuserdata(L, sizeof(Sprite));
	new (ptr) Sprite();
	
	luaL_getmetatable(L, "SpriteMetaTable");
	lua_setmetatable(L, -2);
	return 1;
};
auto DestroySprite = [](lua_State* L) -> int {
	Sprite* ptr = static_cast<Sprite*>(lua_touserdata(L, -1));
	ptr->~Sprite();
	return 0;
};

luaL_newmetatable(L, "SpriteMetaTable");
lua_pushstring(L, "__gc");
lua_pushcfunction(L, DestroySprite);
lua_settable(L, -3);